<?php

namespace yunj\upgrade;

use yunj\enum\UpgradeType;

/**
 * 升级
 * Class Upgrade
 * @package yunj
 */
final class Upgrade {

    /**
     * Notes: 检测
     * Author: Uncle-L
     * Date: 2021/11/5
     * Time: 18:14
     */
    public static function check(): void {
        try {
            self::checkYunjDirExist();
            $type = self::getTypeByVersion();
            if ($type === UpgradeType::NO) return;
            self::backup();
            switch ($type) {
                case UpgradeType::RESET:
                    self::reset();
                    break;
                case UpgradeType::MERGE:
                    self::merge();
                    break;
            }
        } catch (\Exception $e) {
            die("<pre>" . exception_to_str($e) . "</pre>");
        }
    }

    /**
     * Notes: 校验根目录\yunj是否存在
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:24
     * @throws \Exception
     */
    private static function checkYunjDirExist(): void {
        $dirPath = YUNJ_PATH;
        dir_writeable_mk($dirPath);
    }

    /**
     * Notes: 根据版本号获取升级类型
     * Author: Uncle-L
     * Date: 2021/11/5
     * Time: 18:12
     * @return int
     */
    private static function getTypeByVersion(): int {
        $path = YUNJ_PATH . "version.php";
        // 没有版本文件，重置
        if (!is_file($path)) return UpgradeType::RESET;
        $version = include $path;
        // 版本文件没有返回值，重置
        if (!$version || !is_string($version)) return UpgradeType::RESET;
        // 版本一致，不调整
        if ($version === YUNJ_VERSION) return UpgradeType::NO;
        // 版本号小于最新的合并升级，大于重置
        $versionNum = self::versionToNum($version);
        if (!$versionNum) return UpgradeType::RESET;
        $vendorVersionNum = self::versionToNum(YUNJ_VERSION);
        return $versionNum < $vendorVersionNum ? UpgradeType::MERGE : UpgradeType::RESET;
    }

    /**
     * Notes: 版本号字符串转数字
     * Author: Uncle-L
     * Date: 2021/11/5
     * Time: 18:12
     * @param string $version
     * @return int
     */
    private static function versionToNum(string $version): int {
        if (!strstr($version, ".")) return 0;
        list($v1, $v2, $v3) = explode('.', $version);
        if (!$v1 || !is_numeric($v1)) return 0;
        if (!$v2 || !is_numeric($v2)) return 0;
        if (!$v3 || !is_numeric($v3)) return 0;
        return (int)($v1 . $v2 . $v3);
    }

    /**
     * Notes: 备份文件
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:06
     * @throws \Exception
     */
    private static function backup(): void {
        $backupFileList = self::backupFileList();
        if (!$backupFileList) return;
        $backupDirPath = YUNJ_PATH . "temp/backup/";
        dir_writeable_mk($backupDirPath);
        $filename = date("YmdHis");
        $rootPath = str_replace("\\", "/", env("root_path"));
        if (extension_loaded("zip")) {
            // 安装了zip扩展
            $zip = new \ZipArchive();
            $zipFilePath = $backupDirPath . $filename . ".zip";
            $zip->open($zipFilePath, \ZipArchive::CREATE);
            foreach ($backupFileList as $backupFile) $zip->addFile($backupFile, str_replace($rootPath, "", $backupFile));
            $zip->close();
        } else {
            mkdir($backupDirPath . $filename, 0755, true);
            foreach ($backupFileList as $backupFile) {
                $destFilePath = str_replace($rootPath, "{$backupDirPath}{$filename}/", $backupFile);
                file_copy($backupFile, $destFilePath);
            }
        }
    }

    /**
     * Notes: 需要备份的文件列表
     * Author: Uncle-L
     * Date: 2021/11/7
     * Time: 11:56
     * @param string $path
     * @return array
     */
    private static function backupFileList(string $path = YUNJ_PATH): array {
        static $fileList = [];
        $path = str_replace("\\", "/", $path);
        if (substr($path, -1) === "/") $path = substr($path, 0, -1);
        if (is_dir($path)) {
            $fileArr = scandir($path);
            foreach ($fileArr as $file) {
                if (in_array($file, [".", "..", "doc", "temp", ".gitignore"])) continue;
                $filePath = $path . "/{$file}";
                if (is_file($filePath)) {
                    $fileList[] = $filePath;
                } else {
                    self::backupFileList($filePath);
                }
            }
        } else {
            $fileList[] = $path;
        }
        return $fileList;
    }

    /**
     * Notes: 重置
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:34
     * @throws \Exception
     */
    private static function reset(): void {
        // 重置yunj/config文件夹
        self::resetConfigDir();
        // 重置applivation/demo文件夹
        self::resetDemoDir();
        // 重置静态文件夹
        self::resetStaticDir();
        // 重置yunj/doc文件夹
        self::resetDocDir();
        // 重置version.php
        self::resetVersionFile();
    }

    /**
     * Notes: 合并
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:33
     * @throws \Exception
     */
    private static function merge(): void {
        // 合并yunj/config文件夹
        self::mergeConfigDir();
        // 重置applivation/demo文件夹
        self::resetDemoDir();
        // 重置静态文件夹
        self::resetStaticDir();
        // 重置yunj/doc文件夹
        self::resetDocDir();
        // 重置version.php
        self::resetVersionFile();
    }

    /**
     * Notes: 重置config文件夹
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:08
     */
    private static function resetConfigDir(): void {
        dir_delete(YUNJ_PATH . "config");
        dir_copy(YUNJ_VENDOR_SRC_PATH . "config", YUNJ_PATH . "config");
    }

    /**
     * Notes: 合并config文件夹
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:07
     * @throws \Exception
     */
    private static function mergeConfigDir(): void {
        $configFileDir = YUNJ_PATH . "config";
        if (!is_dir($configFileDir)) {
            self::resetConfigDir();
            return;
        }
        $vendorConfigFileDir = YUNJ_VENDOR_SRC_PATH . "config";
        $vendorConfigFileArr = scandir($vendorConfigFileDir);
        foreach ($vendorConfigFileArr as $vendorConfigFile) {
            if ($vendorConfigFile === "." || $vendorConfigFile === "..") continue;
            $vendorConfigFilePath = $vendorConfigFileDir . "/{$vendorConfigFile}";
            $configFilePath = $configFileDir . "/{$vendorConfigFile}";
            if (is_file($configFilePath)) {
                $vendorConfigFileContentFilePath = YUNJ_VENDOR_SRC_PATH . "library/upgrade/config_file_content/{$vendorConfigFile}";
                self::mergeConfigFile($vendorConfigFilePath, $configFilePath, $vendorConfigFileContentFilePath);
            } else {
                file_copy($vendorConfigFilePath, $configFilePath);
            }
        }
    }

    /**
     * Notes: 合并config文件
     * Author: Uncle-L
     * Date: 2021/11/8
     * Time: 14:29
     * @param string $sourcePath [源文件地址]
     * @param string $destPath [目标文件地址]
     * @param string $sourceContentPath [源文件内容地址]
     * @throws \Exception
     */
    private static function mergeConfigFile(string $sourcePath, string $destPath, string $sourceContentPath): void {
        $sourceConfig = include $sourcePath;
        $destConfig = include $destPath;
        $sourceConfigContent = include $sourceContentPath;
        $sourceConfigKeys = array_keys($sourceConfig);
        $destConfigKeys = array_keys($destConfig);
        $diffKeys = array_diff($sourceConfigKeys, $destConfigKeys);
        if (!$diffKeys) return;
        $diffConfigContent = "";
        foreach ($diffKeys as $key) {
            if (!isset($sourceConfigContent[$key])) {
                $configKey = substr($sourcePath, strrpos($sourcePath, "/") + 1, -4) .".". $key;
                throw new \Exception("配置[{$configKey}]无配置默认内容，请联系我们");
            }
            $diffConfigContent .= "\r\n{$sourceConfigContent[$key]}\r\n";
        }
        if (!$diffConfigContent) return;
        $destContent = file_get_contents($destPath);
        // 获取最后一个]并替换后面的内容为空
        $destContent = preg_replace("/(?<=\])(?![\w\W]*\])[\w\W]*/", '', $destContent);
        // 截取最后一个字符前的内容+拼接缺的配置内容+];
        $destContent = substr($destContent, 0, -1) . "\r\n{$diffConfigContent}\r\n];";
        file_put_contents($destPath, $destContent);
    }

    /**
     * Notes: 重置yunj\doc文件夹
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:31
     * @throws \Exception
     */
    private static function resetDocDir(): void {
        $dirPath = YUNJ_PATH . "doc";
        dir_writeable_mk($dirPath);
        dir_delete($dirPath);
        dir_copy(YUNJ_VENDOR_PATH . "doc", $dirPath);
    }

    /**
     * Notes: 重置application\demo文件夹
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:31
     * @throws \Exception
     */
    private static function resetDemoDir(): void {
        $dirPath = env('root_path') . 'application/demo';
        dir_writeable_mk($dirPath);
        dir_delete($dirPath, false, ["{$dirPath}/.gitignore"]);
        dir_copy(YUNJ_VENDOR_PATH . "application/demo", $dirPath);
    }

    /**
     * Notes: 重置静态文件夹
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:32
     * @throws \Exception
     */
    private static function resetStaticDir(): void {
        $dirPath = env('root_path') . 'public/static/yunj';
        dir_writeable_mk($dirPath);
        dir_delete($dirPath, false, ["{$dirPath}/.gitignore"]);
        dir_copy(YUNJ_VENDOR_PATH . "public/static/yunj", $dirPath);
    }

    /**
     * Notes: 重置version.php文件
     * Author: Uncle-L
     * Date: 2021/11/10
     * Time: 18:07
     * @throws \Exception
     */
    private static function resetVersionFile(): void {
        $destPath = YUNJ_PATH . "version.php";
        if (file_exists($destPath)) @unlink($destPath);
        file_copy(YUNJ_VENDOR_SRC_PATH . "version.php", $destPath);
    }


}